#!/usr/bin/env bash
#
# 文 件 名: multi-install-mysqlsh.sh
# 脚本功能: Linux 下安装官方 MySQL Shell
# 文件编码: UTF-8
# 编程语言: Shell Script(Bash 4.x/5.x)
# 编程工具: VSCode/Goland
# 显示语言: 简体中文
# 脚本编写：杨芳超
# 联系邮箱: i-up@qq.com
# 问题反馈: https://seeyon.ren
#

# ******************** 按需配置以下参数 ******************** #

# 软件安装 '根' 目录
installRootDir="/apps"

# 是否写入 PATH 系统环境变量 1="写入"
enableWriteEnvPATH="1"
# 是否写入 LD_LIBRARY_PATH 系统环境变量 1=写入
enableWriteEnvLD="1"
# 系统环境变量配置文件
sysEnvFile="/etc/profile"

# ******************** 按需配置以上参数 ******************** #

# 自定义颜色函数
function oput() {
    # 功能: 自定义颜色并打印标准输出
    # 例子: oput red "系统依赖检查未通过"
    local red_color green_color yellow_color blue_color pink_color white_blue down_blue flash_red res
    # 红色
    red_color='\E[1;31m'
    # 绿色
    green_color='\E[1;32m'
    # 黄色
    yellow_color='\E[1;33m'
    # 蓝色
    blue_color='\E[1;34m'
    # 紫色
    pink_color='\E[1;35m'
    # 白底蓝字
    white_blue='\E[47;34m'
    # 下划线+蓝色
    down_blue='\E[4;36m'
    # 红闪
    flash_red='\E[5;31m'
    # 重置
    res='\E[0m'

    # 限制传入参数必须为2个 否则退出
    if [ $# -ne 2 ]; then echo "Usage $0 {red|yellow|blue|green|pink|wb|db|fr} content"; exit; fi
    case "$1" in
        red | RED ) echo -e "${red_color}$2${res}"; ;;
        yellow | YELLOW ) echo -e "${yellow_color}$2${res}"; ;;
        green | GREEN ) echo -e "${green_color}$2${res}"; ;;
        blue | BLUE ) echo -e "${blue_color}$2${res}"; ;;
        pink | PINK ) echo -e "${pink_color}$2${res}"; ;;
        wb | WB ) echo -e "${white_blue}$2${res}"; ;;
        db | DB ) echo -e "${down_blue}$2${res}"; ;;
        fr | FR ) echo -e "${flash_red}$2${res}"; ;;
        * ) echo -e "请指定一个颜色代码：{red|yellow|blue|green|pink|wb|db|fr}"; ;;
    esac
}

# 语言环境
export LANG=zh_CN.UTF-8
# PATH设置
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
# 脚本目录 -- 与工作目录有区别
scriptDir="$(dirname "$(readlink -f "$0")")"
if ! cd "${scriptDir}"; then oput red "目录切换: 失败 ${scriptDir}" && exit; fi
# 脚本文件
scriptFile="$(basename "$0")"
# 脚本功能
scriptDesc="Linux下安装mysqlsh官方二进制版"
# 系统环境
scriptEnv="Linux + Bash 4.x/5.x + Systemd"
# Bin 目录
# scriptBinDir="${scriptDir}/bin"
# Log 目录
scriptLogDir="${scriptDir}/logs"
if ! mkdir -p "${scriptLogDir}"; then echo "Failed to create script log directory" && exit; fi
# Log 文件
scriptLogFile="${scriptLogDir}/script.log"
# 脚本用法
scriptUsage="bash ${scriptFile} [install|remove]"
# 创建时间
# scriptCreate="2022-10-21 07:00:18"
# 更新时间
scriptUpdate="2022-10-21 15:53:13"

# 操作系统
OS="$(uname | tr '[:upper:]' '[:lower:]')"
if [ "${OS}" != "linux" ]; then echo "${OS} is not supported by this script" && exit; fi

# 平台架构
archType="$(uname -m)"
case "${archType}" in
    # 官方暂未发布 arm64 架构的绿色版
    # "amd64" | "x86_64" | "arm64" | "aarch64" )
        "amd64" | "x86_64" )
        ;;
    * )
        echo; oput red "${archType} is not supported by this script"; echo; exit
        ;;
esac

# 帐号权限
if [ "$(id -un)" != "root" ]; then echo; oput red "please use 'sudo bash ${scriptFile}' to run script"; echo && exit; fi
# bash版本
if [ "${BASH_VERSION:0:1}" -lt 4 ]; then oput red "bash version is too low. must be bash 4.0 or above" && exit; fi

# 生成并清空脚本运行日志
touch "${scriptLogFile}" && true >"${scriptLogFile}"
# 写入当前系统时间和脚本文件名到脚本日志
(echo; echo "$(date '+%Y-%m-%d %H:%M:%S') from ${scriptDir}/${scriptFile}") >>"${scriptLogFile}"

# IP地址 多IP时 只取第一个IP 自动获取 如有需要请手动指定 如 hostAddr="192.168.100.111"
hostAddr="$(LANG=en_US.UTF-8 ip addr | grep -Po '(?<=inet ).*(?=\/)' | grep -v '127.0.0' | head -1)"
# 客户端IP地址 - 自动获取
clientAddr="$(who -u am i 2>/dev/null | awk '{print $NF}'| sed -e 's/[a-zA-Z:()]//g')"
if [ "${clientAddr}" = "" ]; then clientAddr="${hostAddr}"; fi

function get_systemInfo() {
    # 主机
    hostName="$(hostname)"
    # CPU型号
    cpuModel="$(grep name /proc/cpuinfo | uniq | awk -F ':' '{print $(NF)}' | sed 's/^[ \t]*//g')"
    if [ "${cpuModel}" = "" ]; then cpuModel="unrecognized"; fi
    # CPU核数
    cpuLogic="$(grep -c ^processor /proc/cpuinfo 2>/dev/null)"
    # 物理内存 - 总量(低于1G的内存依旧进位1)
    memTotal="$(grep 'MemTotal' /proc/meminfo | awk '{print int($2/1024/1024+1)}')"

    # 物理磁盘
    if command -v lsblk &>/dev/null; then
        # 磁盘总量(GB)
        diskTotalSize="$(lsblk -b 2>>"${scriptLogFile}" | grep disk | awk '{total+=$4/1024/1024/1024}END{print total}')"
    elif command -v parted &>/dev/null; then
        # 磁盘总量(GB)
        diskTotalSize="$(parted -l | grep 'Disk /dev/' | grep -v 'mapper\|dm' | awk '{total+=int($3+0.5)}END{print total}')"
    else
        # 系统未安装 parted/lsblk 时 设定输出为 0
        # disk_count="0"
        diskTotalSize="0"
    fi

    # 操作系统 - 只做简单识别 懒得去把市面上的系统全都调试一遍 ^_^
    if [ -f "/etc/os-release" ]; then
        osRelease="$(awk -F '[="]+' '/^PRETTY_NAME=/{print $2}' /etc/os-release)"
    elif [ -f "/etc/redhat-release" ]; then
        osRelease="$(cat /etc/redhat-release)"
    else
        osRelease="unrecognized operating system"
    fi

    # 内核版本
    osKernelFullVer="$(uname -r | cut -d '-' -f 1)"
    # osKernelMajorVer="$(uname -r | cut -d '-' -f 1 | awk -F . '{print $1}')"
    # osKernelMinorVer="$(uname -r | cut -d '-' -f 1 | awk -F . '{print $2}')"

    # GNU C Library 版本
    osGlibcFullVer="$(getconf GNU_LIBC_VERSION | head -1 | awk '{print $2}')"
    # GNU C Library 主版本号
    osGlibcMajorVer="$(getconf GNU_LIBC_VERSION | head -1 | awk '{print $2}' | awk -F \. '{print $1}')"
    # GNU C Library 次版本号
    osGlibcMinorVer="$(getconf GNU_LIBC_VERSION | head -1 | awk '{print $2}' | awk -F \. '{print $2}')"
}

function set_env_var() {
    case "${action}" in
        "i" )
            echo -e "环境变量: ${envStrLD} \c"
            case "${enableWriteEnvLD}" in
                "1" )
                    if grep -Eq "^export LD_LIBRARY_PATH=${appLibDir}.*LD_LIBRARY_PATH" "${sysEnvFile}"; then
                        oput yellow "已有相同美容 无需再次写入"
                    else
                        echo "${envStrLD}" >>"${sysEnvFile}"
                        oput green "已写入"
                    fi
                    ;;
            esac
            echo -e "环境变量: ${envStrPATH} \c"
            case "${enableWriteEnvPATH}" in
                "1" )
                    if grep -Eq "^export PATH=${appBinDir}.*PATH" "${sysEnvFile}"; then
                        oput yellow "已有相同美容 无需再次写入"
                    else
                        echo "${envStrPATH}" >>"${sysEnvFile}"
                        oput green "已写入"
                    fi
                    ;;
            esac
            ;;
        "r" )
            echo -e "清理环境变量: $(oput blue "export LD_LIBRARY_PATH=${appLibDir}:\$LD_LIBRARY_PATH") \c"
            if grep -q "^export LD_LIBRARY_PATH=${appLibDir}:\$LD_LIBRARY_PATH" "${sysEnvFile}"; then
                sed -i "/^export.*${appName}\/lib:\$LD_LIBRARY_PATH/d" "${sysEnvFile}"
                oput green "完成"
            fi
            echo -e "清理环境变量: $(oput blue "export PATH=${appBinDir}:\$PATH") \c"
            if grep -q "^export PATH=${appBinDir}:\$PATH" "${sysEnvFile}"; then
                sed -i "/^export.*${appName}\/bin:\$PATH/d" "${sysEnvFile}"
                oput green "完成"
            fi
            ;;
    esac
}

# 安装软件
function install_app() {
    echo; oput red "当前任务: 安装 ${appName}"; echo
    # 软件名检查
    if [ "${appName}" = "" ]; then echo "未设置软件名 不能执行安装" && return 1; fi

    # 目录切换
    if ! cd "${scriptDir}" 2>>"${scriptLogFile}"; then echo "目录切换: 失败 ${scriptDir}" && return 1; fi

    local findOpts fileSort findRegex
    findOpts="-maxdepth 1 -regextype \"posix-extended\" -regex"
    if sort --help | grep -Ewq '\-V,'; then
        fileSort="sed 's%^./%%' | sort -V | tail -1"
    else
        fileSort="sed 's%^./%%' | sort | tail -1"
    fi

    findRegex=".*\.\/mysql-shell-.*linux-glibc[0-9].+-x86-(32|64)bit.tar.(g|x)z$"
    findCMDStr="find . ${findOpts} \"${findRegex}\" -type f | ${fileSort}"

    echo -e "找安装包: \c"
    if tarFile="$(eval "${findCMDStr}" 2>>"${scriptLogFile}")"; then
        if [ "${tarFile}" = "" ]; then
            oput red "未找到" && return 1
        else
            # 获取二进制包文件的大小
            tarFileSize="$(du -sh "${tarFile}" | awk '{print $1}')"
            echo "$(oput green "已找到") $(oput blue "${tarFile}") -> $(oput blue "${tarFileSize}")"
        fi
        # 提取二进制包的版本
        # tarFileFullVer="$(echo "${tarFile}" | grep -Eo '[0-9].[0-9].[0-9]+')"
        # 主版本号
        # tarFileMajorVer="$(echo "${tarFileFullVer}" | cut -d '.' -f 1)"
        # 次版本号
        # tarFileMinorVer="$(echo "${tarFileFullVer}" | cut -d '.' -f 2)"
        # 构建版本
        # tarFileBuildVer="$(echo "${tarFileFullVer}" | cut -d '.' -f 3)"
        # 获取二进制包文件的大小
        tarFileSize="$(du -sh "${tarFile}" | awk '{print $1}')"
        # 获取二进制包文件的后缀名
        case "${tarFile:0-7}" in
            ".tar.xz" )
                tarFileFormat="txz"
                ;;
            ".tar.gz" )
                tarFileFormat="tgz"
                ;;
        esac
        tarGlibcFullVer="$(echo "${tarFile}" | grep -Eo 'glibc[0-9.]+' | grep -Eo '[0-9.]+')"
        tarGlibcMajorVer="$(echo "${tarGlibcFullVer}" | awk -F . '{print $1}')"
        tarGlibcMinorVer="$(echo "${tarGlibcFullVer}" | awk -F . '{print $2}')"

        echo -e "版本检查: \c"
        # Major = Major
        if [ "${tarGlibcMajorVer}" -eq "${osGlibcMajorVer}" ] ; then
            if [ "${tarGlibcMinorVer}" -le "${osGlibcMinorVer}" ] ; then
                # tarMinor >= osMinor
                echo "glibc $(oput green "主版本号满足条件") $(oput green "小版本号满足条件")"
            else
                # tarMinor < osMinor
                echo "glibc $(oput green "主版本号满足条件") $(oput red "小版本号不满足条件")"
                return 1
            fi
        else
            echo "glibc $(oput red "主版本号不满足条件")"
            return 1
        fi
    else
        return 1
    fi

    local ucp
    # 解包后的目录相对路径 ucp=use-compress-program
    case "${tarFileFormat}" in
        "tgz" )
            ucp="gzip"
            unpackDir="${tarFile%.tar.gz}"
            ;;
        "txz" )
            ucp="xz"
            unpackDir="${tarFile%.tar.xz}"
            ;;
        * )
            unpackDir=""
            oput red "解包失败: 二进制安装包文件后缀名必须是 tar.xz 或 tar.gz"
            return 1
            ;;
    esac
    if ! command -v "${ucp}" &>/dev/null; then oput red "解包失败: ${ucp} 命令未找到" && return 1; fi

    # 如果解包目录已存在 则删除
    if [ -d "${installRootDir}/${unpackDir}" ] && [ "${unpackDir}" != "" ]; then
        echo -e "删除目录: ${installRootDir}/${unpackDir} \c"
        if rm -rf "${installRootDir:?parameter null or not set}/${unpackDir:?parameter null or not set}"; then
            oput green "成功"
        else
            echo "失败"
            return 1
        fi
    fi
    # 解压命令
    unpackCMD="tar -xf ${tarFile} -C ${installRootDir}"
    # 如果需要手动指定调用的解压程序 则按需调整后 使用下行解压命令
    # unpackCMD="tar --use-compress-program=xz -xf ${tarFile} -C ${installRootDir}"

    # 解压二进制包文件
    echo -e "正在解包: ${tarFile} \c"
    if eval "${unpackCMD}" 2>>"${scriptLogFile}"; then
        oput green "成功"
    else
        oput red "失败"
        return 1
    fi

    echo -e "删除目录: ${appInstallDir} \c"
    if rm -rf "${appInstallDir}"; then
        oput green "成功"
    else
        oput red "失败"
        return 1
    fi
    # do not prompt before overwriting
    echo -e "移动目录: ${installRootDir}/${unpackDir} to ${appInstallDir} \c"
    if mv "${installRootDir}/${unpackDir}" "${appInstallDir}"; then
        oput green "成功"
    else
        oput red "失败"
        return 1
    fi

    echo -e "授予权限: chmod -R 755 ${appInstallDir} \c"
    # 如果设置为 750 其他用户不能访问
    if chmod -R 755 "${appInstallDir}"; then oput green "成功"; else echo "失败" && return 1; fi

    set_env_var

    echo; echo "$(oput green "安装完成") 执行以下命令查看使用帮助 $(oput blue "${appBinDir}/mysqlsh --help")"

    # mysqlsh --sql root:i4Seeyon@192.168.100.100:3306/g6n -e "select config_value from ctp_config where config_item='productEdition';" 2>/dev/null
}

# 移除
function remove_app() {
    echo; oput red "当前任务: 卸载 ${appName}"; echo
    # 执行删除
    if [ ! -d "${appInstallDir}" ]; then
        echo "卸载失败: 目录不存在 ${appInstallDir}"; echo && return 1
    fi

    echo -e "删除安装目录: ${appInstallDir} \c"
    if rm -rf "${appInstallDir}"; then oput green "成功"; else oput red "失败" && return 1; fi

    set_env_var

    echo; oput green "${appName} 卸载完成"
}

# 打印主机相关信息
function print_host_info() {
    echo "登录信息: $(id -un)@${hostAddr} from ${clientAddr}"
    echo "主机名称: ${hostName}"
    echo "CPU 型号: ${cpuModel}"
    echo "硬件概览: ${cpuLogic} 核CPU + ${memTotal} GB内存 + ${diskTotalSize:=0} GB磁盘"
    echo "操作系统: ${osRelease}"
    echo "Bash版本: ${BASH_VERSION}"
    echo "系统内核: ${osKernelFullVer}"
    echo "GNU C 库: ${osGlibcFullVer}"
}

# 打印脚本信息
function print_scriptInfo() {
    echo
    echo "脚本文件: ${scriptFile}"
    echo "脚本功能: ${scriptDesc}"
    echo "系统支持: ${scriptEnv}"
    echo "脚本用法: ${scriptUsage}"
    echo "更新时间: ${scriptUpdate}"
    echo
    print_host_info
}

# 未传参时的脚本提示
function main_prompt() {
    # 输出信息提示
    cat <<EOF

版本支持: mysqlsh >= 8.0.11 + glibc >= 2.12
使用说明: 将脚本和 MySQL Shell 官方二进制安装包放在同级目录 终端下传参执行脚本

执行安装: bash ${scriptFile} install
执行卸载: bash ${scriptFile} remove

官方文档: https://dev.mysql.com/doc/mysql-shell/8.0/en/
官方下载: https://dev.mysql.com/downloads/shell/
EOF
}

appName="mysqlsh"
appInstallDir="${installRootDir}/${appName}"
appBinDir="${installRootDir}/${appName}/bin"
appLibDir="${installRootDir}/${appName}/lib"

envStrLD="export LD_LIBRARY_PATH=${appLibDir}:\$LD_LIBRARY_PATH"
envStrPATH="export PATH=${appBinDir}:\$PATH"

get_systemInfo
print_scriptInfo

case "$1" in
    "install" )
        action="i"
        if ! install_app 2>&1 | tee "${scriptLogFile}"; then exit; fi
        ;;
    "remove" )
        action="r"
        if ! remove_app 2>&1 | tee "${scriptLogFile}"; then exit; fi
        ;;
    * )
        main_prompt
        ;;
esac

echo
